import { useCallback, useMemo, useRef, useState } from 'react';
import type { ClipboardEvent } from 'react';
import { useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { imageUpload } from './utils';
import { useToastContext } from '@/app/components/base/toast';
import { ALLOW_FILE_EXTENSIONS, TransferMethod } from '@/types/app';
import type { ImageFile, VisionSettings } from '@/types/app';

export const useImageFiles = () => {
  const params = useParams();
  const { t } = useTranslation();
  const { notify } = useToastContext();
  const [files, setFiles] = useState<ImageFile[]>([]);
  const filesRef = useRef<ImageFile[]>([]);

  const handleUpload = (imageFile: ImageFile) => {
    const files = filesRef.current;
    const index = files.findIndex((file) => file._id === imageFile._id);

    if (index > -1) {
      const currentFile = files[index];
      const newFiles = [...files.slice(0, index), { ...currentFile, ...imageFile }, ...files.slice(index + 1)];
      setFiles(newFiles);
      filesRef.current = newFiles;
    } else {
      const newFiles = [...files, imageFile];
      setFiles(newFiles);
      filesRef.current = newFiles;
    }
  };
  const handleRemove = (imageFileId: string) => {
    const files = filesRef.current;
    const index = files.findIndex((file) => file._id === imageFileId);

    if (index > -1) {
      const currentFile = files[index];
      const newFiles = [...files.slice(0, index), { ...currentFile, deleted: true }, ...files.slice(index + 1)];
      setFiles(newFiles);
      filesRef.current = newFiles;
    }
  };
  const handleImageLinkLoadError = (imageFileId: string) => {
    const files = filesRef.current;
    const index = files.findIndex((file) => file._id === imageFileId);

    if (index > -1) {
      const currentFile = files[index];
      const newFiles = [...files.slice(0, index), { ...currentFile, progress: -1 }, ...files.slice(index + 1)];
      filesRef.current = newFiles;
      setFiles(newFiles);
    }
  };
  const handleImageLinkLoadSuccess = (imageFileId: string) => {
    const files = filesRef.current;
    const index = files.findIndex((file) => file._id === imageFileId);

    if (index > -1) {
      const currentImageFile = files[index];
      const newFiles = [...files.slice(0, index), { ...currentImageFile, progress: 100 }, ...files.slice(index + 1)];
      filesRef.current = newFiles;
      setFiles(newFiles);
    }
  };
  const handleReUpload = (imageFileId: string) => {
    const files = filesRef.current;
    const index = files.findIndex((file) => file._id === imageFileId);

    if (index > -1) {
      const currentImageFile = files[index];
      imageUpload(
        {
          file: currentImageFile.file!,
          onProgressCallback: (progress) => {
            const newFiles = [...files.slice(0, index), { ...currentImageFile, progress }, ...files.slice(index + 1)];
            filesRef.current = newFiles;
            setFiles(newFiles);
          },
          onSuccessCallback: (res) => {
            const newFiles = [...files.slice(0, index), { ...currentImageFile, fileId: res.id, progress: 100 }, ...files.slice(index + 1)];
            filesRef.current = newFiles;
            setFiles(newFiles);
          },
          onErrorCallback: () => {
            notify({ type: 'error', message: t('common.imageUploader.uploadFromComputerUploadError') });
            const newFiles = [...files.slice(0, index), { ...currentImageFile, progress: -1 }, ...files.slice(index + 1)];
            filesRef.current = newFiles;
            setFiles(newFiles);
          },
        },
        !!params.token,
      );
    }
  };

  const handleClear = () => {
    setFiles([]);
    filesRef.current = [];
  };

  const filteredFiles = useMemo(() => {
    return files.filter((file) => !file.deleted);
  }, [files]);

  return {
    files: filteredFiles,
    onUpload: handleUpload,
    onRemove: handleRemove,
    onImageLinkLoadError: handleImageLinkLoadError,
    onImageLinkLoadSuccess: handleImageLinkLoadSuccess,
    onReUpload: handleReUpload,
    onClear: handleClear,
  };
};

type useLocalUploaderProps = {
  disabled?: boolean;
  limit?: number;
  onUpload: (imageFile: ImageFile) => void;
};

export const useLocalFileUploader = ({ limit, disabled = false, onUpload }: useLocalUploaderProps) => {
  const { notify } = useToastContext();
  const params = useParams();
  const { t } = useTranslation();

  const handleLocalFileUpload = useCallback(
    (file: File) => {
      if (disabled) {
        // TODO: leave some warnings?
        return;
      }

      if (!ALLOW_FILE_EXTENSIONS.includes(file.type.split('/')[1])) return;

      if (limit && file.size > limit * 1024 * 1024) {
        notify({ type: 'error', message: t('common.imageUploader.uploadFromComputerLimit', { size: limit }) });
        return;
      }

      const reader = new FileReader();
      reader.addEventListener(
        'load',
        () => {
          const imageFile = {
            type: TransferMethod.local_file,
            _id: `${Date.now()}`,
            fileId: '',
            file,
            url: reader.result as string,
            base64Url: reader.result as string,
            progress: 0,
          };
          onUpload(imageFile);
          imageUpload(
            {
              file: imageFile.file,
              onProgressCallback: (progress) => {
                onUpload({ ...imageFile, progress });
              },
              onSuccessCallback: (res) => {
                onUpload({ ...imageFile, fileId: res.id, progress: 100 });
              },
              onErrorCallback: () => {
                notify({ type: 'error', message: t('common.imageUploader.uploadFromComputerUploadError') });
                onUpload({ ...imageFile, progress: -1 });
              },
            },
            !!params.token,
          );
        },
        false,
      );
      reader.addEventListener(
        'error',
        () => {
          notify({ type: 'error', message: t('common.imageUploader.uploadFromComputerReadError') });
        },
        false,
      );
      reader.readAsDataURL(file);
    },
    [disabled, limit, notify, t, onUpload, params.token],
  );

  return { disabled, handleLocalFileUpload };
};

type useClipboardUploaderProps = {
  files: ImageFile[];
  visionConfig?: VisionSettings;
  onUpload: (imageFile: ImageFile) => void;
};

export const useClipboardUploader = ({ visionConfig, onUpload, files }: useClipboardUploaderProps) => {
  const allowLocalUpload = visionConfig?.transfer_methods?.includes(TransferMethod.local_file);
  const disabled = useMemo(
    () => !visionConfig || !visionConfig?.enabled || !allowLocalUpload || files.length >= visionConfig.number_limits!,
    [allowLocalUpload, files.length, visionConfig],
  );
  const limit = useMemo(() => (visionConfig ? +visionConfig.image_file_size_limit! : 0), [visionConfig]);
  const { handleLocalFileUpload } = useLocalFileUploader({ limit, onUpload, disabled });

  const handleClipboardPaste = useCallback(
    (e: ClipboardEvent<HTMLTextAreaElement>) => {
      // reserve native text copy behavior
      const file = e.clipboardData?.files[0];
      // when copied file, prevent default action
      if (file) {
        e.preventDefault();
        handleLocalFileUpload(file);
      }
    },
    [handleLocalFileUpload],
  );

  return {
    onPaste: handleClipboardPaste,
  };
};

type useDraggableUploaderProps = {
  files: ImageFile[];
  visionConfig?: VisionSettings;
  onUpload: (imageFile: ImageFile) => void;
};

export const useDraggableUploader = <T extends HTMLElement>({ visionConfig, onUpload, files }: useDraggableUploaderProps) => {
  const allowLocalUpload = visionConfig?.transfer_methods?.includes(TransferMethod.local_file);
  const disabled = useMemo(
    () => !visionConfig || !visionConfig?.enabled || !allowLocalUpload || files.length >= visionConfig.number_limits!,
    [allowLocalUpload, files.length, visionConfig],
  );
  const limit = useMemo(() => (visionConfig ? +visionConfig.image_file_size_limit! : 0), [visionConfig]);
  const { handleLocalFileUpload } = useLocalFileUploader({ disabled, onUpload, limit });
  const [isDragActive, setIsDragActive] = useState(false);

  const handleDragEnter = useCallback(
    (e: React.DragEvent<T>) => {
      e.preventDefault();
      e.stopPropagation();
      if (!disabled) setIsDragActive(true);
    },
    [disabled],
  );

  const handleDragOver = useCallback((e: React.DragEvent<T>) => {
    e.preventDefault();
    e.stopPropagation();
  }, []);

  const handleDragLeave = useCallback((e: React.DragEvent<T>) => {
    e.preventDefault();
    e.stopPropagation();
    setIsDragActive(false);
  }, []);

  const handleDrop = useCallback(
    (e: React.DragEvent<T>) => {
      e.preventDefault();
      e.stopPropagation();
      setIsDragActive(false);

      const file = e.dataTransfer.files[0];

      if (!file) return;

      handleLocalFileUpload(file);
    },
    [handleLocalFileUpload],
  );

  return {
    onDragEnter: handleDragEnter,
    onDragOver: handleDragOver,
    onDragLeave: handleDragLeave,
    onDrop: handleDrop,
    isDragActive,
  };
};
